<template>
  <div class="ocr-container">
    <!-- <div class="ocr-header">
      <div class="title">过程卡图像识别</div>
    </div> -->
    <div class="ocr-content">
      <div class="cameras">
        <img
          id="main-camera"
          :src="`${scannerSeverUrl}/video=stream&camidx=0`"
          alt="主摄像头"
        />
        <!-- <img
          id="deputy-camera"
          src="http://127.0.0.1:38088/video=stream&camidx=1"
          alt="副摄像头"
        /> -->
      </div>
      <div class="identify-result">
        <div class="ocr-img">
          <div v-if="imageUrl" class="image-preview">
            <img :src="imageUrl" alt="预览图片" @click="openImageView" />
          </div>
          <div v-else class="no-image">
            <p>暂无图片预览</p>
          </div>
        </div>
        <div class="ocr-result">
          <div v-if="loading" class="loading">
            <Spin size="large" fix>
              <Icon type="ios-loading" size="18" class="spin-icon-load"></Icon>
              <div>正在识别...</div>
            </Spin>
          </div>
          <div v-else-if="!result" class="no-result">
            <p>暂无识别结果</p>
          </div>
          <div v-else class="result-content">
            <Alert v-if="error" type="error">{{ error }}</Alert>
            <div class="result-con" v-else>
              <div v-if="isTextResult" class="text-result">
                <div class="cost-time">识别耗时: {{ resultTime }} 秒</div>
                <Input
                  v-model="textResult"
                  type="textarea"
                  :rows="15"
                  readonly
                ></Input>
                <div class="copy-button-container">
                  <Button type="primary" @click="copyText">复制文本</Button>
                </div>
              </div>
              <div v-else class="detailed-result">
                <p>识别耗时: {{ resultTime }} 秒</p>
                <p>识别到 {{ detailedResult.length }} 个文本块:</p>
                <Table
                  :columns="resultColumns"
                  :data="detailedResult"
                  border
                  :height="400"
                ></Table>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="identify-count">
      <div class="count">
        <!-- 识别次数、识别耗时、识别结果数、识别结果大小 -->
        <span class="count-info"
          >本次识别耗时 {{ resultTime }} 秒、识别并提交
          {{ submitTimes }} 次</span
        >
      </div>
      <div class="identify">
        <!-- <Button type="primary" :loading="loading">上传图片</Button> -->
        <!-- <Button type="primary" :loading="loading">扫描</Button>
        <Button type="primary" :loading="loading">提交</Button> -->
        <Button type="primary" @click="performOCR" :loading="loading"
          >一键识别</Button
        >
        <Button @click="clearResults">清空结果</Button>
      </div>
    </div>
    <div class="table-info">
      <div v-if="!result" class="no-result">
        <p>暂无识别结果</p>
      </div>
      <div class="result-view" style="width: 100%" v-else>
        <Alert v-if="error" type="error">{{ error }}</Alert>
        <!-- <o-layout
          :listenType="listenType"
          v-else-if="!error && isLayoutConfig"
          :config="layoutConfig"
          ref="baseLayout"
        ></o-layout> -->
        <component
          v-if="isLayoutConfig"
          :is="$srv.utils.findComponent(layoutConfig.name)"
          :listenType="listenType"
          v-bind="{ config: layoutConfig.config }"
          ref="baseLayout"
        ></component>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { T_srv, Tsrv, ifs, ts } from '@/services';
import axios from 'axios';
import { Component, Vue } from 'vue-property-decorator';
// import { readConfig, scannerSeverUrl } from './config';
import { readConfig } from './config';

interface OCRDetailedResult {
  text: string;
  score: number;
  box: number[][];
  end: string;
}

interface OCRResponse {
  code: number;
  data: OCRDetailedResult[] | string;
  time: number;
  timestamp: number;
}

interface EngineOption {
  title: string;
  type: string;
  default: any;
  optionsList?: any[][];
  toolTip?: string;
}

interface EngineParams {
  loaded: boolean;
  language?: EngineOption;
  cls?: EngineOption;
  limitSideLen?: EngineOption;
  tbpuParser?: EngineOption;
}

@Component({
  name: 'cProcessCard'
})
export default class cProcessCard extends Vue {
  @T_srv.prop({
    type: Object,
    default: () => {}
  })
  config!: any;

  private imageFile: File | null = null;

  [x: string]: any;

  private _config: any;

  private listenType = 'default';

  private imageUrl: string = '';

  private imageBase64: string = '';

  private loading: boolean = false;

  private result: OCRResponse | null = null;

  private error: string = '';

  private textResult: string = '';

  private detailedResult: OCRDetailedResult[] = [];

  public regCustEventName: Array<string> = [];

  private apiUrl: string = 'http://47.113.200.153:32081';

  private scannerSeverUrl: string = 'http://127.0.0.1:38088';

  private apiError: string = '';

  private engineParams: EngineParams = {
    loaded: false
  };

  private submitTimes: number = 0;

  public baseLayout: any = null;

  private layoutConfig = null;

  private cardNumber = '-1';

  private cardID = '-1';

  private options = {
    'data.format': 'text',
    'ocr.language': 'models/config_chinese.txt',
    'ocr.cls': false,
    'ocr.limit_side_len': 960,
    'tbpu.parser': 'single_code',
    'tbpu.ignoreArea': [
      [
        [0, 0],
        [1080, 200]
      ]
    ]
  };

  private resultColumns = [
    {
      title: '文本内容',
      key: 'text',
      ellipsis: true
    },
    {
      title: '置信度',
      key: 'score',
      width: 100,
      render: (h: any, params: any) => {
        return h('span', `${(params.row.score * 100).toFixed(2)}%`);
      }
    },
    {
      title: '结束符',
      key: 'end',
      width: 100,
      render: (h: any, params: any) => {
        if (!params.row.end) return h('span', '无');
        if (params.row.end === '\n') return h('span', '换行');
        if (params.row.end === ' ') return h('span', '空格');
        return h('span', params.row.end);
      }
    }
  ];

  get isLayoutConfig(): boolean {
    return this.layoutConfig !== null;
  }

  private get resultTime(): string {
    if (!this.result) return '0';
    return this.result.time.toFixed(2);
  }

  private get isTextResult(): boolean {
    return this.result !== null && typeof this.result.data === 'string';
  }

  created() {
    this._config = Tsrv.utils.clone(this.config, true);
    this.init();
  }

  mounted() {
    if (this._config) {
      this.listenType = this._config['listenType'] || 'default';
    }
    document.addEventListener('keydown', this.handleKeyDown);
  }

  destroyed() {
    document.removeEventListener('keydown', this.handleKeyDown);
  }

  private handleKeyDown(event: any) {
    if (event.key === 'F8') {
      event.preventDefault();
      this.performOCR();
    }
  }

  private init() {
    this.getProceeCardConf();
    // this.openMainCamera();
  }

  private handleBeforeUpload(file: File): boolean {
    const isImage = file.type.startsWith('image/');
    if (!isImage) {
      this.$Message.error('只能上传图片文件');
      return false;
    }

    const reader = new FileReader();
    reader.onload = (e: any) => {
      this.imageUrl = e.target.result;
      // 提取Base64编码
      this.imageBase64 = e.target.result.split(',')[1];
    };
    reader.readAsDataURL(file);
    this.imageFile = file;

    return false; // 阻止自动上传
  }

  private performOCR() {
    this.takePicture()
      .then(async (res: any) => {
        this.imageBase64 = res.data.photoBase64;
        this.imageUrl = `data:image/jpg;base64,${res.data.photoBase64}`;

        if (!this.imageBase64) {
          this.$Message.warning('请先选择图片');
          return;
        }

        try {
          this.loading = true;
          this.error = '';
          this.result = null;

          const url = `${this.$config?.ocrURL ?? this.apiUrl}/api/ocr`;
          const data = {
            base64: this.imageBase64,
            options: this.options
          };

          const response = await fetch(url, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
          });

          if (!response.ok) {
            throw new Error(`HTTP 错误! 状态: ${response.status}`);
          }

          const result = (await response.json()) as OCRResponse;
          this.result = result;

          if (result.code === 100) {
            // 成功
            if (typeof result.data === 'string') {
              this.textResult = result.data;
            } else {
              this.detailedResult = result.data;
            }
            this.$Message.success('OCR识别成功');
            // 识别成功——同步表格数据
            this.handleORCData();
          } else if (result.code === 101) {
            // 无文本
            this.error = '图片中未检测到文本';
            this.$Message.info(this.error);
          } else {
            // 其他错误
            this.error =
              typeof result.data === 'string' ? result.data : '识别失败';
            this.$Message.error(this.error);
          }
        } catch (err) {
          console.error('OCR识别失败:', err);
          this.error = `OCR识别失败: ${err.message}`;
          this.$Message.error(this.error);
        } finally {
          this.loading = false;
        }
      })
      .catch((err: any) => {});
  }

  private clearResults() {
    this.imageFile = null;
    this.imageUrl = '';
    this.imageBase64 = '';
    this.result = null;
    this.error = '';
    this.textResult = '';
    this.detailedResult = [];
  }

  private copyText() {
    if (!this.textResult) return;

    const textArea = document.createElement('textarea');
    textArea.value = this.textResult;
    document.body.appendChild(textArea);
    textArea.select();

    try {
      document.execCommand('copy');
      this.$Message.success('复制成功');
    } catch (err) {
      this.$Message.error('复制失败');
    }

    document.body.removeChild(textArea);
  }

  private setTableFilter(filter: any) {
    if (!this.isLayoutConfig) {
      return;
    }
    this.baseLayout = this.$refs.baseLayout;
    // type: 'layout' 使用
    // const cardContainerRef = this.baseLayout?.$children[0]?.$children[0];
    // if (
    //   !Tsrv.noValue(cardContainerRef) &&
    //   !Tsrv.noValue(cardContainerRef?.$children[0])
    // ) {
    //   const cardRef = cardContainerRef.$children[0];
    //   cardRef.GT_editor.addInitFilter(filter);
    //   cardRef.commitEvent('reload');
    // }
    // type: 'component' 使用
    this.baseLayout.GT_editor.addInitFilter(filter);
    this.baseLayout.commitEvent('reload');
  }

  private handleORCData() {
    const textResult = this.textResult;
    const textArr = textResult.split(/\s+/).filter(item => item !== '');
    Tsrv.getSnowIDFmtStr().then(id => {
      this.cardID = id;
      const insertData: ifs.I_fv[] = [];
      insertData.push(
        new ts.T_fv()
          .setField(Tsrv.globalVar.fieldName.ID)
          .setValue(this.cardID)
      );
      for (let i = 0; i < readConfig.length; i++) {
        const el = readConfig[i];

        // 检测 扫描来的label 与 配置的label 是否一致，当相似度超过60%则认为一致
        // const findIndex = textArr.findIndex(item => item === el.label);
        const findIndex = textArr.findIndex(
          item =>
            item === el.label ||
            (el.label.includes(item) && item.length / el.label.length > 0.6)
        );

        if (findIndex > -1) {
          const value = el.direction
            ? textArr[findIndex + 1]
            : textArr[findIndex - 1];
          if (
            readConfig.findIndex(
              item =>
                item.label === value ||
                (item.label.includes(value) &&
                  value.length / item.label.length > 0.6)
            ) > -1
          ) {
            continue;
          }
          insertData.push(new ts.T_fv().setField(el.field).setValue(value));
        }
      }
      // cardID
      this.loadProcessCardData(insertData)
        .then(res => {
          ++this.submitTimes;
          const filter = new ts.T_filter();
          filter.filter = filter.parseFvs([
            new ts.T_fv()
              .setField(Tsrv.globalVar.fieldName.ID)
              .setValue(this.cardID)
          ]);
          this.setTableFilter(filter);
        })
        .catch(err => {
          const filter = new ts.T_filter();
          const f = filter.parseFvs([
            new ts.T_fv().setField(Tsrv.globalVar.fieldName.ID).setValue(-1)
          ]);
          this.setTableFilter(f);
        });
    });
  }

  private loadProcessCardData(insertData: ifs.I_fv[]): Promise<boolean> {
    return new Promise((resolve, reject) => {
      const dbType = Tsrv.globalVar.dbType.MYSQL;
      // const tableName = 'process_card';
      const tableName = 'aps_process_card';
      const rule = {} as ifs.I_rule;
      const ins = new ts.T_ctdInsRec();
      ins.setScope({}).setBaseDataGeneral(rule, {}, ...insertData);
      Tsrv.addRows(tableName, dbType, rule, ins)
        .then(([ok, res, err]) => {
          if (!ok || !err) {
            this.$msg(err ?? res.msg);
            resolve(false);
          } else {
            this.$msg(res.msg);
            resolve(true);
          }
        })
        .catch(er => {
          reject(er);
        });
    });
  }

  /**
   * 获取流程卡的实表配置
   */
  private getProceeCardConf() {
    this.$store
      .dispatch('getLayoutConfigAsync', {
        funid: null,
        // field: 'process_card',
        field: 'aps_process_card',
        // type: 'layout'
        type: 'component'
      })
      .then((res: any) => {
        this.layoutConfig = Tsrv.utils.clone(res.current, true);
      })
      .catch((err: any) => {
        console.error(err);
      });
  }

  /**
   * 打开主摄像头
   */
  private openMainCamera() {
    // 主摄像头
    (document.getElementById(
      'main-camera'
    ) as HTMLImageElement).src = `${this.scannerSeverUrl}/video=stream&camidx=0?1`;
    // // 副摄像头
    // (document.getElementById('deputy-camera') as HTMLImageElement).src =
    //   'http://127.0.0.1:38088/video=stream&camidx=1?1';
  }

  private takePicture() {
    const data = {
      filepath: 'base64',
      rotate: '0',
      cutpage: '0',
      camidx: '0',
      ColorMode: '0',
      quality: '3'
    };
    return axios.post(
      `${this.scannerSeverUrl}/video=grabimage`,
      JSON.stringify(data)
    );
  }

  private openImageView() {
    Tsrv.utils.createModal('o-image-viewer', {
      imageSrc: this.imageUrl
    });
  }
}
</script>

<style lang="less" scoped>
.ocr-container {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: 1fr;
  // grid-template-rows: 35px 1fr 50px 1fr;
  grid-template-rows: 4fr 50px 5fr;
  grid-gap: 15px; /* 减小网格间距 */
  justify-content: center;
  align-items: start; /* 顶部对齐 */
  padding: 15px; /* 减小外边距 */
  background: #f5f7f9;

  .ocr-header {
    display: flex;
    justify-content: start;
    align-items: center;
    height: 100%;
    width: 100%;
    padding: 0 15px; /* 减小内边距 */
    border-bottom: 1px solid #e8e8e8;

    .title {
      font-size: 18px; /* 减小标题大小 */
      color: #17233d;
      margin-right: 10px; /* 减小标题右侧间距 */
      font-weight: 600;
    }
  }

  .ocr-content {
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    grid-gap: 15px; /* 减小网格间距 */
    overflow: hidden;

    .cameras {
      width: 100%;
      height: 100%;
      overflow: auto;
      background-color: #fff;
      border-radius: 5px;
      display: flex;
      justify-content: center;
      align-items: center;

      #main-camera {
        max-width: 100%;
        max-height: 100%;
        // width: 100%;
        // height: 100%;
        width: auto;
        height: auto;
        object-fit: contain;
        border: 1px solid #eee;
      }
    }

    .identify-result {
      height: 100%;
      width: 100%;
      display: grid;
      grid-template-columns: 1fr 1fr;
      grid-template-rows: 1fr;
      grid-gap: 15px; /* 减小网格间距 */

      .ocr-img,
      .ocr-result {
        height: 100%;
        width: 100%;
        padding: 10px; /* 减小内边距 */
        overflow: auto;
        background-color: #fff;
        border-radius: 5px;
      }

      .ocr-img {
        .image-preview {
          width: 100%;
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;

          img {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
            border: 1px solid #eee;
            cursor: zoom-in;
          }
        }
      }

      .ocr-result {
        .result-content {
          height: 100%;
          width: 100%;
          display: flex;
          flex-direction: column;

          .result-con {
            flex: 1;
            .text-result {
              display: flex;
              flex-direction: column;
              height: 100%;

              .cost-time {
                width: 100%;
                height: 30px;
                display: flex;
                justify-content: start;
                align-items: center;
                padding-left: 10px;
                flex-wrap: nowrap;
                overflow: auto;
              }

              /deep/ .ivu-input-wrapper {
                flex: 1;
                display: flex;
                flex-direction: column;
                margin-bottom: 10px;

                textarea {
                  flex: 1;
                  resize: none;
                }
              }

              .copy-button-container {
                width: 100%;
                height: 35px;
                display: flex;
                justify-content: end;
                align-items: center;
              }
            }
          }
        }
      }

      .no-image,
      .no-result {
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        background: #f8f8f9;
        color: #808695;
        border-radius: 5px;
      }
    }
  }

  .identify-count {
    height: 100%;
    width: 100%;
    display: flex;
    justify-content: space-around;
    align-items: center;

    .count + .identify {
      margin-left: 15px;
    }

    .count,
    .identify {
      flex: 1;
      height: 100%;
      width: 100%;
      padding: 0 15px;
      display: flex;
      justify-content: start;
      align-items: center;
      border-radius: 5px;
      background-color: #fff;
    }

    .count {
      display: flex;

      .count-info {
        max-height: 100%;
        overflow: auto;
      }
    }

    .identify {
      display: flex;
      flex-direction: row;
      justify-content: start;
      align-items: center;
      flex-wrap: nowrap;
      overflow: auto;

      .ivu-btn + .ivu-btn {
        margin-left: 10px;
      }
    }
  }

  .table-info {
    height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #fff;

    .no-result {
      height: calc(100% - 20px);
      width: calc(100% - 20px);
      display: flex;
      justify-content: center;
      align-items: center;
      background: #f8f8f9;
      color: #808695;
      border-radius: 5px;
      margin: 10px;
    }

    .result-view {
      display: flex;
      flex-direction: column;
      height: 100%;
      width: 100%;
    }
  }
}

.spin-icon-load {
  animation: ani-spin 1s linear infinite;
}
@keyframes ani-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>
